#pragma once 


//--------------------------------------------------------------------------------------------------------------------
#define TASK_MANAGER_IMPL_LOCK_SINGLE_SHARED	0	// WORKING ... and smooth (in my real world case at least)
#define TASK_MANAGER_IMPL_LOCK					0	// WORKING ...
#define TASK_MANAGER_IMPL_LOCKFREE				1	// WORKING ... FAST in Tests only... In my real world case it's not smooth enough<!!!>  -- too much contention when TASK_MANAGER_WORKERS_SLEEP_WAIT == 1 

#define TASK_MANAGER_WORKERS_SLEEP_WAIT			1
//#define TASK_MANAGER_INTERNALS_OPTIMIZED_FOR_SPEED	1
//#define TASK_MANAGER_INTERNALS_OPTIMIZED_FOR_SIZE	0
//--------------------------------------------------------------------------------------------------------------------

namespace Melting
{
namespace Parallel
{
	//--------------------------------------------------------------------------------------------------------------------
	typedef INT32 TaskID;
	//--------------------------------------------------------------------------------------------------------------------				
	struct TaskQueueInfo
	{
		UINT32		miAffinityMask;			///< Affinity of the Tasks in the queue
		UINT32		miPriority;				///< Priority of the Tasks in the queue
		UINT32		miMaxTasks;				///< Max number of Tasks in the queue
	};
	//--------------------------------------------------------------------------------------------------------------------
	class TaskWorker; 
	//--------------------------------------------------------------------------------------------------------------------	
	/**
		Class TaskManager 
		TODO : How to use
	*/ 
	class TaskManager 
	{
	public:

		/** CTOR  
			_iNbWorkerThreads : Nb Workers the TaskManager will use
			_iMaxTasks :		Maximum number of Task alive at the same time
			_iMaxPriority :		Maximum number of level of priority for Tasks
			_iDefaultPriority : Default priority for a Task		
			_iNbTaskQueueInfos: Number of TaskQueueInfos that are passed via _aInfos
			_aTaskQueueInfos:	Array of TaskQueueInfos, describing the requested TaskQueue -- it will be sorted by affinity-priority order */
		TaskManager	( UINT32 _iNbWorkerThreads, UINT32 _iMaxTasks, UINT32 _iMaxPriority, UINT32 _iDefaultPriority, UINT32 _iNbTaskQueueInfos, TaskQueueInfo* _aTaskQueueInfos );

		/// DTOR
		~TaskManager( void );

		static const TaskID		NO_TASK				= -1;
		static const UINT32		UNDEFINED_PRIORITY	= 0xFFFFFFFF;
		static const UINT32		UNDEFINED_AFFINITY	= 0xFFFFFFFF; 

		/// Run the TaskManager, and start its workers -- returns true if successful
		bool				Run					( void ); 

		/// Returns the number of worker threads in use
		inline UINT32		GetNbWorkers		( void ) const;

		/// Add a Task to the scheduling
		inline TaskID		AddTask				( Task* _pTask, TaskID _parentID = NO_TASK, TaskID _dependencyID = NO_TASK, UINT32 _iPriority = UNDEFINED_PRIORITY, UINT32 _iAffinityMask = UNDEFINED_AFFINITY );

		/// Add a Task, and make sure it cannot finish before EndAddGroupTask( _taskID ) is called 
		inline TaskID		BeginAddGroupTask	( Task* _pTask = nullptr, TaskID _parentID = NO_TASK, TaskID _dependencyID = NO_TASK, UINT32 _iPriority = UNDEFINED_PRIORITY, UINT32 _iAffinityMask = UNDEFINED_AFFINITY );
		/// Notify that the _taskID is ready, and can now be finished
		inline void			EndAddGroupTask		( TaskID _taskID );

		/// Wait on the Task associated to the given _taskID, helping the Workers in the execution of their Tasks until it becomes "done"
		void				WaitOnTask			( TaskID _taskID, bool _bHelpOnWait = true );

	
		/// Force each threads (workers + calling main thread) to be on a specific cpu logical core, returns true on success
		/// WARNING : call this from the main thread
		/// WARNING : call this AFTER	Run() 
		/// WARNING : make sure the (nb worker threads + nb main threads ) <= (nb logical cores)
		bool				ForceSpecificThreadAffinities( void );

		/// Set a name to a Task, for debugging purpose
		inline void			SetDebugName( TaskID _id, const char* _strDebugName ); 

		// void				ForceNbWorkers( UINT32 );
		// void				ResetNbWorkers( UINT32 ); 

		inline void			HardReset( void ); 





	private:
		
		friend class TaskWorker;
		friend class PendingQueue;
		
		//--------------------------------------------------------------------------------------------------------------------
		// WARNING : this struct is 64bytes wide (yup, just like most cache lines), and must remain like this for memory access speed reason
		struct TaskInfo
		{
			TaskID				mID;				///< ID of the Task
			Task				mTask;				///< Task to execute	- 32 bytes wide struct
			TaskID				mParentID;			///< ID of the parent Task 
			AtomicCounter		miPendingTasks;		///< Number of pending Tasks, including this one
			TaskID				mDependencyID;		///< ID of the Task before which this Task should not be started

			// TODO ? remove that in from the struct in a later impl, we can simply use the info at schedule time only
			UINT32				miPriority;			///< Priority of this Task
			UINT32				miAffinityMask;		///< Bitset of 'threads' on which the Task can be executed

			const char*			mstrDebugName;		///< Name of Task, for debugginb - the 4bytes of padding were available to make sure the struct would be 64bytes wide anywway
		};

		/// Create a new Task with the given parameters, and return its TaskID
		TaskID				RegisterTask		( Task* _pTask, TaskID _parentID, TaskID _dependencyID, UINT32 _iPriority, UINT32 _iAffinityMask, UINT32 _iPendingTasks );
		
		/// Add the TaskInfo to the appropriate queue of ready tasks
		void				AddToReadyQueue		( TaskInfo* _pTaskInfo );		
		/// Add the TaskInfo to the shared queue of waiting tasks
		void				AddToWaitQueue		( TaskInfo* _pTaskInfo );
  
		/// Get the TaskInfo associated to the given _taskID
		inline TaskInfo*	GetTaskInfo			( TaskID _taskID ) const;
		/// Is the Task associated to the given _taskID finished ?
		inline bool			IsTaskDone			( TaskInfo* _pTaskInfo ) const;
		/// Set a Task done, updating its parent Tasks as well
		void				SetTaskDone			( TaskInfo* _pTaskInfo );

		/// Can the given Task start or is it blocked by its dependency ?
		inline bool			IsReadyToStart		( TaskInfo* _pTaskInfo ) const;
		
		/// Take the next free TaskInfo in the TaskInfo pool
		TaskInfo*			TakeFreeTaskInfo	( void );
		/// Take from the next ready Task, with respect to affinity and priority
		TaskInfo*			TakeNextReadyTask	( UINT32 _iAffinityMask ); 

		/// Put the thread to sleep if there is no Task to process - WARNING : Call this from a worker only
		inline void			WaitForAvailableTask( void );

		/// Try to run the scheduling slice, and returns true upon success, false if some other thread is already doing it
		bool				TryToSchedule		( void );
 
		/// Stub function for 'empty' task
		static void			DoNothing			( const TaskFuncParams& _rParams );
		static Task			sEmptyTask;


 		
		UINT32				miNbWorkers;		///< Number of Workers currently running
		TaskWorker*			maWorkers;			///< Collection of Workers
		UINT32				miMaxTasks;			///< 
		AtomicCounter		miNextTask;			///< Next TaskInfo slot index to take when registering a new Task
		TaskInfo*			maTaskInfos;		///< 
		//TaskInfo			maTaskInfos2[ 10000000 ]; ///< nice for debugging
		UINT32				miMaxPriority;		///< Maximum priority for a Task
		UINT32				miDefaultPriority;	///< Default priority for a Task	

	//-----------------------------------------------------------
	#if	TASK_MANAGER_WORKERS_SLEEP_WAIT	
		AtomicCounter		miTaskCount;			///< 
		Mutex				mMutexTaskPending;
		ConditionVariable	mConditionTaskPending;
	#endif

	//-----------------------------------------------------------
	//-----------------------------------------------------------
	#if TASK_MANAGER_IMPL_LOCK_SINGLE_SHARED
		Mutex				mMutex; 		
		std::vector<TaskID>	mPendingQueue;
	#endif
	//-----------------------------------------------------------
	//-----------------------------------------------------------
	#if TASK_MANAGER_IMPL_LOCK

		SpinLock					mLockScheduling;		///< Lock to allow only 1 thread to process the TryToSchedule() 
		Mutex						mLockDependencies;
/*/
		std::vector<TaskInfo*>		mDependentTasks;		///< Collection of TaskInfo waiting for their dependency to complete
/*/
		LockFreeMemoryPool			mNodeMemoryPool;		///< Pool of LockFreeStack<TaskInfo*>::Node - to be used with mDependentTasks 
		LockFreeStack<TaskInfo*>	mDependentTasks;
//*/		

		struct TaskQueueID
		{
			union
			{
				UINT64 miKey;
				struct 
				{
					UINT32	miAffinityMask;	///< Affinity managed by the queue
					UINT32	miPriority;		///< Priority managed by the queue
				};
			};
		};
		typedef std::vector< TaskInfo* > TaskQueue;
		struct TaskQueueSlot
		{
			TaskQueueID		mID;		///< ID of this TaskQueueSlot
			Mutex			mLock;		///< Lock to protect the access
			TaskQueue		mQueue;	///< TaskQueue containing TaskInfo ready to be executed, and that match the mID
		};

		UINT32						miNbReadyQueues;		///< Number of TaskQueueSlot s containing Task ready to be executed
		TaskQueueSlot*				maReadyQueues;			///< Collection of TaskQueueSlot containing Task ready to be executed

	#endif
	//-----------------------------------------------------------
	//-----------------------------------------------------------
	#if TASK_MANAGER_IMPL_LOCKFREE
	
		SpinLock					mLockScheduling;		///< Lock to allow only 1 thread to process the TryToSchedule() 
		LockFreeMemoryPool			mNodeMemoryPool;		///< Pool of LockFreeStack<TaskInfo*>::Node - to be used with mDependentTasks 
		LockFreeStack<TaskInfo*>	mDependentTasks;		///< Collection of TaskInfo waiting for their dependency to complete

		struct TaskQueueID
		{
			union
			{
				UINT64 miKey;
				struct 
				{
					UINT32	miAffinityMask;	///< Affinity managed by the queue
					UINT32	miPriority;		///< Priority managed by the queue
				};
			};
		};
		typedef LockFreeQueue< TaskInfo*, nullptr > TaskQueue;
		struct TaskQueueSlot
		{
			TaskQueueID		mID;		///< ID of this TaskQueueSlot
			TaskQueue*		mpQueue;	///< TaskQueue containing TaskInfo ready to be executed, and that match the mID
		};

		UINT32						miNbReadyQueues;		///< Number of TaskQueueSlot s containing Task ready to be executed
		TaskQueueSlot*				maReadyQueues;			///< Collection of TaskQueueSlot containing Task ready to be executed
	#endif
	//-----------------------------------------------------------
	//-----------------------------------------------------------
	};
 
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
 
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	UINT32 TaskManager::GetNbWorkers( void ) const
	{
		return miNbWorkers;
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	TaskID TaskManager::AddTask( Task* _pTask, TaskID _parentID, TaskID _dependencyID, UINT32 _iPriority, UINT32 _iAffinityMask )
	{
		return RegisterTask( _pTask, _parentID, _dependencyID, _iPriority, _iAffinityMask, 1 );
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	TaskID TaskManager::BeginAddGroupTask( Task* _pTask, TaskID _parentID, TaskID _dependencyID, UINT32 _iPriority, UINT32 _iAffinityMask )
	{
		// set the open count to 2 => 1 for this Task + 1 for the "begin / end" processing
		return RegisterTask( _pTask, _parentID, _dependencyID, _iPriority, _iAffinityMask, 2 );
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	void TaskManager::EndAddGroupTask( TaskID _taskID )
	{	
		TaskInfo* pTaskInfo = GetTaskInfo( _taskID );
		MB_ASSERT( nullptr != pTaskInfo, "Invalid TaskID : %d\n", _taskID );

		pTaskInfo->miPendingTasks.Decrement();

		//-----------------
		// postcondition
		MB_ASSERT( pTaskInfo->miPendingTasks.Get() >= 0, "Invalid miOpenTasks counter : %d\n", pTaskInfo->miPendingTasks.Get() );
		//-----------------
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	bool TaskManager::IsTaskDone( TaskInfo* _pTaskInfo ) const
	{
		//-------------------
		// precondition
		MB_ASSERT( nullptr != _pTaskInfo, "Invalid TaskInfo ptr\n" );		
		//-------------------

		return 0 == _pTaskInfo->miPendingTasks.Get();
	}


	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	TaskManager::TaskInfo* TaskManager::GetTaskInfo( TaskID _taskID ) const
	{	
		//-----------------
		// precondition
		// FIXME : check if I can have TaskID a UINT32
		MB_ASSERT( 0 <= _taskID && _taskID < miMaxTasks, "TaskID out of range ! 0 <= %d < max %d\n", _taskID, miMaxTasks - 1 );
		//-----------------

		return & maTaskInfos[ _taskID ];
		//return ( _taskID >= 0 && _taskID < miMaxTasks) ? & maTaskInfos[ _taskID ] : nullptr;
	}
		
	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	bool TaskManager::IsReadyToStart( TaskInfo* _pTaskInfo ) const
	{
		return (_pTaskInfo->mDependencyID == NO_TASK || IsTaskDone( GetTaskInfo( _pTaskInfo->mDependencyID ) ) );	
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	void TaskManager::WaitForAvailableTask( void )
	{	
		#if TASK_MANAGER_WORKERS_SLEEP_WAIT
		{
			mMutexTaskPending.Lock();
			{
				while( 0 == miTaskCount.Get() ) 
				{
					mConditionTaskPending.Wait( mMutexTaskPending );
				}
			}
			mMutexTaskPending.Unlock();
		}
		#else
		{
//			if( ! bScheduled )
			{		
				Parallel::YieldProcessor();
			}
		}
		#endif
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	void	TaskManager::SetDebugName( TaskID _id, const char* _strDebugName )
	{
	#ifndef __FINALROM
//		GetTaskInfo( _id )->mstrDebugName = _strDebugName;
	#endif
	}

	//--------------------------------------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------------------------------------
	void TaskManager::HardReset( void )
	{
	#ifndef __FINALROM		

		#if TASK_MANAGER_WORKERS_SLEEP_WAIT
		{
			miTaskCount.Set( 0 ); 
		}
		#endif
		miNextTask.Set( 0 );
				 

		for( UINT32 i = 0; i < miMaxTasks; i++ )
		{
			maTaskInfos[ i ].mID			= i;	
			maTaskInfos[ i ].mParentID		= NO_TASK;		
			maTaskInfos[ i ].mDependencyID	= NO_TASK;		
			maTaskInfos[ i ].miPriority		= miDefaultPriority;		
			maTaskInfos[ i ].miAffinityMask = 0;		

			maTaskInfos[ i ].mstrDebugName	= 0;
		}
	#endif
	}
}
}// namespace Melting